fix(api): page the public anchor listing on (created_at, id), not created_at alone - #9733
Conversation
…ated_at alone loadPublicLedgerAnchors's keyset cursor filtered `WHERE created_at < ?` while ordering `BY created_at DESC, id DESC`. Because the ORDER BY tiebreaks on id but the WHERE filtered on created_at alone, any group of rows sharing one created_at that straddled a page boundary was silently skipped -- the next page started strictly before that timestamp, so the tied remainder was unreachable on any page. Ties are real: recordLedgerAnchorAttempt defaults createdAt to nowIso() (millisecond precision) and one scheduler tick records an attempt per backend, so several rows land on the same created_at. An attempt -- including a failure, the row this public health endpoint exists to make observable -- could therefore be permanently unreachable through the paginated API while sitting in the table. Make the cursor match the sort key: the WHERE clause becomes the standard row-comparison form `(created_at < ? OR (created_at = ? AND id < ?))`, and nextBefore now encodes both (created_at, id) as one opaque `created_at|id` string. The field stays `string | null`, so the route and published response shape are unchanged for a caller that only round-trips the value. An unparseable `before` falls back to the first page rather than throwing, since this is a public route. The single prepared statement and the fetch-one-extra-row limit+1 technique are unchanged. Closes JSONbored#9715
|
🚨 Contributor flagged. Click here for more info: Superagent Dashboard |
|
Warning ⏸️ LoopOver review result - manual review recommendedReview updated: 2026-07-29 05:30:25 UTC
Review summary Nits — 4 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionPartially addressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Decision record
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9733 +/- ##
==========================================
+ Coverage 76.51% 76.53% +0.01%
==========================================
Files 282 283 +1
Lines 59378 59421 +43
Branches 6542 6556 +14
==========================================
+ Hits 45435 45478 +43
Misses 13661 13661
Partials 282 282
Flags with carried forward coverage won't be shown. Click here to find out more.
|
|
Held for manual review: the gate and required CI are green, but GitHub reports this pull request's mergeable state as |
What & why
Closes #9715.
GET /v1/public/decision-ledger/anchorsis the public listing that makes anchoring's own health apublicly checkable fact. But
loadPublicLedgerAnchors's keyset cursor didn't match its sort key: itfiltered
WHERE created_at < ?while orderingBY created_at DESC, id DESC. Because the ORDER BYtiebreaks on
idbut the WHERE filtered oncreated_atalone, any group of rows sharing onecreated_atthat straddled a page boundary was silently skipped — the next page started strictlybefore that timestamp, so the tied remainder was never returned on any page.
Ties are real, not hypothetical:
recordLedgerAnchorAttemptdefaultscreatedAttonowIso()(millisecond precision) and one scheduler tick records an attempt per backend, so several rows land on
the same
created_at. An attempt — including a failure, the row this endpoint exists to surface —could be permanently unreachable through the paginated API while sitting in the table.
Change
(created_at < ? OR (created_at = ? AND id < ?)), preservingORDER BY created_at DESC, id DESC.nextBeforenow encodes both(created_at, id)as one opaquecreated_at|idstring. The fieldstays a single
string | null, so the route and published response shape are unchanged for a callerthat only round-trips the value.
beforeis treated as "no cursor" (first page), never throws — this is anunauthenticated public route.
LedgerAnchorListFilter.beforedoc comment is updated (it no longer means "strictly older thanthis ISO timestamp").
limit + 1technique are unchanged, perthe issue's required pattern.
Validation
created_at, paged in 2s — asserts all four arereachable across page boundaries (was silently dropping the tied remainder before).
|-malformedbeforevalues fall back to the first page rather than throwing.created_at < ?fails the tie regression test.